`timescale 10 ns / 1 ns

`define DATA_WIDTH 32

module alu(
  input [`DATA_WIDTH - 1:0] alu_src0,
  input [`DATA_WIDTH - 1:0] alu_src1,
  input [             12:0] alu_op,

  output                     alu_overflow,
  output                     alu_carryout,
  output                     alu_zero,
  output [`DATA_WIDTH - 1:0] alu_result,
  output [`DATA_WIDTH - 1:0] alu_addr
);

  // one-hot op code
  wire op_add;
  wire op_sub;
  wire op_and;
  wire op_nor;
  wire op_or;
  wire op_xor;
  wire op_slt;
  wire op_sltu;
  wire op_sll;
  wire op_sra;
  wire op_srl;
  wire op_lui;
  wire op_match;

  assign op_add  = alu_op[ 0];
  assign op_sub  = alu_op[ 1];
  assign op_and  = alu_op[ 2];
  assign op_nor  = alu_op[ 3];
  assign op_or   = alu_op[ 4];
  assign op_xor  = alu_op[ 5];
  assign op_slt  = alu_op[ 6];
  assign op_sltu = alu_op[ 7];
  assign op_sll  = alu_op[ 8];
  assign op_sra  = alu_op[ 9];
  assign op_srl  = alu_op[10];
  assign op_lui  = alu_op[11];
  assign op_match = alu_op[12];

  // results
  wire [`DATA_WIDTH - 1:0] add_sub_result;
  wire [`DATA_WIDTH - 1:0] and_result;
  wire [`DATA_WIDTH - 1:0] nor_result;
  wire [`DATA_WIDTH - 1:0] or_result;
  wire [`DATA_WIDTH - 1:0] xor_result;
  wire [`DATA_WIDTH - 1:0] slt_result;
  wire [`DATA_WIDTH - 1:0] sltu_result;
  wire [`DATA_WIDTH - 1:0] sll_result;
  wire [`DATA_WIDTH - 1:0] sra_result;
  wire [`DATA_WIDTH - 1:0] srl_result;
  wire [`DATA_WIDTH - 1:0] lui_result;
  wire [`DATA_WIDTH - 1:0] match_result;

  // adder
  wire                     add_sub_judge;
  wire [`DATA_WIDTH    :0] adder_op1;
  wire [`DATA_WIDTH    :0] adder_op2;
  wire [`DATA_WIDTH    :0] adder_out;
  wire                     adder_cout;

  assign add_sub_judge           = op_sub | op_slt | op_sltu;
  assign adder_op1               = {alu_src0, add_sub_judge};
  assign adder_op2               = add_sub_judge ? {~alu_src1, add_sub_judge} : {alu_src1, add_sub_judge};
  // assign {adder_cout, adder_out} = adder_op1 + adder_op2;

  adder32 mycpu_adder(
    .A(adder_op1[32:1]),
    .B(adder_op2[32:1]),
    .C(add_sub_judge),
    .S(adder_out[32:1]),
    .C32(adder_cout)
  );

  // adder_32 mycpu_adder (
  //   .A(adder_op1[32:1]),
  //   .B(adder_op2[32:1]),
  //   .C_IN(add_sub_judge),
  //   .S(adder_out[32:1]),
  //   .C_OUT(adder_cout)
  // );

  // results assignment
  assign add_sub_result = adder_out[32:1];
  assign and_result     = alu_src0 & alu_src1;
  assign nor_result     = ~(alu_src0 | alu_src1);
  assign or_result      = alu_src0 | alu_src1;
  assign xor_result     = alu_src0 ^ alu_src1;

  assign slt_result[31:1]  = {31{1'b0}};
  assign slt_result[0]     = (adder_op1[32] & adder_op2[32]) | ((alu_src0[31] ~^ alu_src1[31]) & adder_out[32]);
  assign sltu_result[31:1] = {31{1'b0}};
  assign sltu_result[0]    = ~(op_sub ^ adder_cout);

  // shifter
  wire [`DATA_WIDTH - 1:0] amt;
  wire [`DATA_WIDTH - 1:0] shift_src;
  wire [`DATA_WIDTH - 1:0] shift_out;
  wire [`DATA_WIDTH - 1:0] sra_mask;
  assign amt = alu_src0 & 32'h0000001f;

  assign shift_src = op_sll ? {
    alu_src1[ 0], alu_src1[ 1], alu_src1[ 2], alu_src1[ 3],
    alu_src1[ 4], alu_src1[ 5], alu_src1[ 6], alu_src1[ 7],
    alu_src1[ 8], alu_src1[ 9], alu_src1[10], alu_src1[11],
    alu_src1[12], alu_src1[13], alu_src1[14], alu_src1[15],
    alu_src1[16], alu_src1[17], alu_src1[18], alu_src1[19],
    alu_src1[20], alu_src1[21], alu_src1[22], alu_src1[23],
    alu_src1[24], alu_src1[25], alu_src1[26], alu_src1[27],
    alu_src1[28], alu_src1[29], alu_src1[30], alu_src1[31]
  } : alu_src1;
  assign shift_out = shift_src >> amt;

  assign sra_mask = ~(32'hffff_ffff >> amt);
  assign sra_result = ({32{alu_src1[31]}} & sra_mask) | shift_out;
  assign srl_result = shift_out;
  assign sll_result = {
    shift_out[ 0], shift_out[ 1], shift_out[ 2], shift_out[ 3],
    shift_out[ 4], shift_out[ 5], shift_out[ 6], shift_out[ 7],
    shift_out[ 8], shift_out[ 9], shift_out[10], shift_out[11],
    shift_out[12], shift_out[13], shift_out[14], shift_out[15],
    shift_out[16], shift_out[17], shift_out[18], shift_out[19],
    shift_out[20], shift_out[21], shift_out[22], shift_out[23],
    shift_out[24], shift_out[25], shift_out[26], shift_out[27],
    shift_out[28], shift_out[29], shift_out[30], shift_out[31]
  };

  assign lui_result = {alu_src1[15:0], 16'h0000};

  wire [24:0] match;
  wire [4:0] decode;
  genvar i;
  generate
    for (i = 0; i < 25; i = i + 1) begin
      assign match[i] = ~(|(((alu_src1 >> i) & 32'h000000ff) ^ alu_src0[7:0]));
    end
  endgenerate
  encoder_24_5 mycpu_encoder_24_5(
    .in(match),
    .out(decode)
  );
  assign match_result = (decode == 5'h1f) ? 32'hffffffff : {27'd0, decode};

  // outputs
  assign alu_carryout = op_sub ^ adder_cout;
  assign alu_overflow = (adder_op1[32] ^~ adder_op2[32]) & (adder_out[32] ^ adder_op1[32]);
  assign alu_zero = ~(|alu_result);

  assign alu_result = ({32{op_add }} & add_sub_result)
                    | ({32{op_sub }} & add_sub_result)
                    | ({32{op_and }} &     and_result)
                    | ({32{op_nor }} &     nor_result)
                    | ({32{op_or  }} &      or_result)
                    | ({32{op_xor }} &     xor_result)
                    | ({32{op_slt }} &     slt_result)
                    | ({32{op_sltu}} &    sltu_result)
                    | ({32{op_sll }} &     sll_result)
                    | ({32{op_sra }} &     sra_result)
                    | ({32{op_srl }} &     srl_result)
                    | ({32{op_lui }} &     lui_result)
                    | ({32{op_match}} & match_result); 
  assign alu_addr = add_sub_result;
    
endmodule
